#ifndef DATETIME_H
#define DATETIME_H

#include <ctime>
#include <exception>
#include <iostream>

namespace biosignal {

/**
 * Represents a specific instant time for a location
 */
class TimeOffset;
class TimePoint {
public:
    TimePoint();
    explicit TimePoint(int year, int month, int day, int hour = 0, int minute = 0,
                       int second = 0, int useconds = 0);
    TimePoint& operator=(const TimePoint& rhs);
    TimePoint& operator+=(const TimeOffset& rhs);
    TimePoint& operator-=(const TimeOffset& rhs);
    TimePoint operator+(const TimeOffset& offset) const;
    TimePoint operator-(const TimeOffset& offset) const;
    TimeOffset operator-(const TimePoint& other) const;
    bool operator==(const TimePoint &other) const;
    bool operator!=(const TimePoint &other) const;

    bool operator>=(const TimePoint &other) const;
    bool operator<(const TimePoint &other) const;

    int year() const;
    int month() const;
    int day() const;
    int hour() const;
    int minute() const;
    int second() const;

    virtual std::ostream &display(std::ostream & out) const;
    friend std::ostream &operator<<(std::ostream &out,
                                    const TimePoint& date_time);
protected:
    std::time_t m_time;
    struct std::tm m_timeinfo;
    int m_useconds;
    void update_time(time_t time, int usec);
};

class TimeOffset {
public:
    explicit TimeOffset(int years = 0, int months = 0, int days = 0, int hours = 0,
                        int minutes = 0, int seconds = 0, int microseconds = 0);
    TimeOffset& operator=(const TimeOffset& rhs);
    TimeOffset& operator+=(const TimeOffset& rhs);
    TimeOffset& operator-=(const TimeOffset& rhs);
    TimeOffset operator+(const TimeOffset& other);
    TimeOffset operator-(const TimeOffset& other);
    TimeOffset operator*(double factor);
    bool operator==(const TimeOffset &other) const;
    bool operator!=(const TimeOffset &other) const;

    int days() const;
    int hours() const;
    int minutes() const;
    int seconds() const;
    int microseconds() const;

    long full_days() const;
    long full_hours() const;
    long full_minutes() const;
    long full_seconds() const;

    long total_microseconds() const;

    double total_seconds() const;

    bool is_negative() const;
    static TimeOffset Seconds(double seconds);

    virtual std::ostream &display(std::ostream & out) const;
    friend std::ostream &operator<<(std::ostream &out,
                                    const TimeOffset& date_time);

private:
    struct std::tm m_timeinfo;
    int m_useconds;

};

class NotIntersectsException : public std::exception {
  virtual const char* what() {
    return "NotIntersectsException";
  }
};

class TimePeriod {
public:
    TimePeriod();
    TimePeriod(TimePoint date_time, TimeOffset duration);
    /*
     * TimePeriod : [begin, end)
     */
    TimePeriod(TimePoint begin, TimePoint end);
    TimePoint begin() const;
    TimePoint end() const;

    TimeOffset duration() const;
    bool contains(const TimePoint& time_point) const;
    bool contains(const TimePeriod& time_period) const;

    TimePeriod insersection(const TimePeriod& tp) const;
    bool insersects(const TimePeriod& tp) const;

    void duration(TimeOffset duration);
    void move(TimeOffset offset);
    void begin(TimePoint begin);
    
    bool operator==(const TimePeriod &other) const;
    bool operator!=(const TimePeriod &other) const;
    
    virtual std::ostream &display(std::ostream & out) const;
    friend std::ostream &operator<<(std::ostream &out,
                                    const TimeOffset& date_time);
private:
    TimePoint m_begin;
    TimeOffset m_duration;
};

inline std::ostream &operator<<(std::ostream &out, const TimePoint& date_time) {
    return date_time.display(out);
}

inline std::ostream &operator<<(std::ostream &out,
                                const TimeOffset& time_offset) {
    return time_offset.display(out);
}

inline std::ostream &operator<<(std::ostream &out,
                                const TimePeriod& time_period) {
    return time_period.display(out);
}

}
#endif // DATETIME_H
